using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using dnAnalytics.Math;
using dnAnalytics.LinearAlgebra;
using dnAnalytics.LinearAlgebra.Decomposition;
using dnAnalytics.LinearAlgebra.IO;
using dnAnalytics.Exceptions;

namespace dnAnalytics.UnitTests.LinearAlgebra.Decomposition
{
    public abstract class AbstractCholeskyTest
    {
        public abstract void GetDecompositionClass(String file, out Matrix matrix, out Cholesky lu);
        private Cholesky cholesky;
        private Cholesky notPD;
        private Matrix matrix;
        private Matrix notPDMatrix;

        public abstract Matrix GetMatrix(int order);
        public abstract Matrix GetMatrix(int rows, int columns);
        public abstract Vector GetVector(int size);

        [TestFixtureSetUp]
        public void SetUp()
        {
            MatrixMarketReader mmr = new MatrixMarketReader();
            GetDecompositionClass("../../TestMatrices/hilbert_real_symmetric_array_10.matrix_market", out matrix, out cholesky);
            GetDecompositionClass("../../TestMatrices/gear_integer_general_coordinate_100.matrix_market", out notPDMatrix, out notPD);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CholeskyConstructorNull()
        {
            Matrix matrix = null;
            Cholesky cholesky = new Cholesky(matrix);
        }

        [Test]
        [ExpectedException(typeof(NotSquareMatrixException))]
        public void CholeskyConstructor()
        {
            Matrix matrix = GetMatrix(3, 2);
            Cholesky cholesky = new Cholesky(matrix);
        }

        [Test]
        public void PositiveDefinite()
        {
            Assert.IsTrue(cholesky.IsPositiveDefinite());
        }

        [Test]
        public void NotPositiveDefinite()
        {
            Assert.IsFalse(notPD.IsPositiveDefinite());
        }

        [Test]
        public void Factor()
        {
            Matrix factor = cholesky.Factor();
            Matrix result = factor * factor.Transpose();

            double error = 0;
            for (int i = 0; i < result.Rows; i++)
            {
                for (int j = 0; j < result.Columns; j++)
                {
                    error = 0;
                    if (matrix[i, j] != 0)
                    {
                        error = System.Math.Abs((result[i, j] - matrix[i, j]) / matrix[i, j]);
                    }
                    else
                    {
                        error = System.Math.Abs(result[i, j] - matrix[i, j]);
                    }

                    Assert.IsTrue(error < Constants.ACCEPTABLE_ERROR);
                }
            }
        }

        [Test]
        public void FactorResult()
        {
            Matrix factor = MatrixBuilder.CreateMatrix(matrix.Rows);
            cholesky.Factor(factor);
            Matrix result = factor * factor.Transpose();

            double error = 0;
            for (int i = 0; i < result.Rows; i++)
            {
                for (int j = 0; j < result.Columns; j++)
                {
                    error = 0;
                    if (matrix[i, j] != 0)
                    {
                        error = System.Math.Abs((result[i, j] - matrix[i, j]) / matrix[i, j]);
                    }
                    else
                    {
                        error = System.Math.Abs(result[i, j] - matrix[i, j]);
                    }

                    Assert.IsTrue(error < Constants.ACCEPTABLE_ERROR);
                }
            }
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void FactorResultNull()
        {
            Matrix result = null;
            cholesky.Factor(result);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void FactorResultNotConformable1()
        {
            Matrix result = GetMatrix(matrix.Rows + 1, matrix.Rows);
            cholesky.Factor(result);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void FactorResultNotConformable2()
        {
            Matrix result = GetMatrix(matrix.Rows, matrix.Rows + 1);
            cholesky.Factor(result);
        }

        [Test]
        public void Determinant()
        {
            double expected = 2.183E-53;
            double actual = cholesky.Determinant();
            Assert.AreEqual(expected, actual, Constants.IO_ACCEPTABLE_ERROR);
        }

        [Test]
        public void SolveMatrix()
        {
            Matrix identity = MatrixBuilder.CreateIdentityMatrix(matrix.Rows);
            Matrix result = cholesky.Solve(identity);
            result = matrix * result;

            for (int i = 0; i < result.Rows; i++)
            {
                for (int j = 0; j < result.Columns; j++)
                {
                    if (i == j)
                    {
                        Assert.AreEqual(1, result[i, j], Constants.ACCEPTABLE_CHOLESKY_SOLVE_ERROR);
                    }
                    else
                    {
                        Assert.AreEqual(0, result[i, j], Constants.ACCEPTABLE_CHOLESKY_SOLVE_ERROR);
                    }
                }
            }
        }

        [Test]
        [ExpectedException(typeof(NotPositiveDefiniteException))]
        public void SolveMatrixNotPositiveDefiniteMatrix()
        {
            Matrix identity = GetMatrix(notPDMatrix.Rows);
            notPD.Solve(identity);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SolveMatrixNull()
        {
            Matrix identity = null;
            cholesky.Solve(identity);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SolveMatrixNotConformable()
        {
            Matrix identity = GetMatrix(matrix.Rows + 1);
            cholesky.Solve(identity);
        }

        [Test]
        public void SolveMatrixResult()
        {
            Matrix identity = MatrixBuilder.CreateIdentityMatrix(matrix.Rows);
            Matrix result = GetMatrix(matrix.Rows);
            cholesky.Solve(identity, result);
            result = matrix * result;

            for (int i = 0; i < result.Rows; i++)
            {
                for (int j = 0; j < result.Columns; j++)
                {
                    if (i == j)
                    {
                        Assert.AreEqual(1, result[i, j], Constants.ACCEPTABLE_CHOLESKY_SOLVE_ERROR);
                    }
                    else
                    {
                        Assert.AreEqual(0, result[i, j], Constants.ACCEPTABLE_CHOLESKY_SOLVE_ERROR);
                    }
                }
            }
        }

        [Test]
        [ExpectedException(typeof(NotPositiveDefiniteException))]
        public void SolveMatrixNotPositiveDefiniteMatrixResult()
        {
            Matrix identity = GetMatrix(notPDMatrix.Rows);
            Matrix result = GetMatrix(notPDMatrix.Rows);
            notPD.Solve(identity, result);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SolveMatrixNullResult1()
        {
            Matrix identity = null;
            Matrix result = GetMatrix(matrix.Rows);
            cholesky.Solve(identity, result);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SolveMatrixNullResult2()
        {
            Matrix identity = GetMatrix(matrix.Rows);
            Matrix result = null;
            cholesky.Solve(identity, result);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SolveMatrixNotConformableResult1()
        {
            Matrix identity = GetMatrix(matrix.Rows + 1);
            Matrix result = GetMatrix(matrix.Rows);
            cholesky.Solve(identity, result);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SolveMatrixNotConformableResult2()
        {
            Matrix identity = GetMatrix(matrix.Rows);
            Matrix result = GetMatrix(matrix.Rows + 1);
            cholesky.Solve(identity, result);
        }

        [Test]
        public void SolveVector()
        {
            Vector vector = VectorBuilder.CreateVector(matrix.Rows);
            for (int i = 0; i < vector.Count; i++)
            {
                vector[i] = (double)(i + 1) / (i + 2);
            }
            Vector result = cholesky.Solve(vector);
            Vector resultVector = matrix * result;
            for (int i = 0; i < resultVector.Count; i++)
            {
                Assert.AreEqual((double)(i + 1) / (i + 2), resultVector[i], Constants.ACCEPTABLE_CHOLESKY_SOLVE_ERROR);
            }
        }

        [Test]
        [ExpectedException(typeof(NotPositiveDefiniteException))]
        public void SolveVectorNotPositiveDefinite()
        {
            Vector vector = GetVector(notPDMatrix.Rows);
            notPD.Solve(vector);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SolveVectorNull()
        {
            Matrix identity = null;
            cholesky.Solve(identity);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SolveVectorNotConformable()
        {
            Matrix identity = GetMatrix(matrix.Rows + 1);
            cholesky.Solve(identity);
        }

        [Test]
        public void SolveVectorResult()
        {
            Vector vector = VectorBuilder.CreateVector(matrix.Rows);
            for (int i = 0; i < vector.Count; i++)
            {
                vector[i] = (double)(i + 1) / (i + 2);
            }

            Vector result = VectorBuilder.CreateVector(matrix.Rows);
            cholesky.Solve(vector, result);
            Vector resultVector = matrix * result;
            for (int i = 0; i < resultVector.Count; i++)
            {
                Assert.AreEqual((double)(i + 1) / (i + 2), resultVector[i], Constants.ACCEPTABLE_CHOLESKY_SOLVE_ERROR);
            }
        }

        [Test]
        [ExpectedException(typeof(NotPositiveDefiniteException))]
        public void SolveVectorNotPositiveDefiniteResult()
        {
            Vector vector = GetVector(notPDMatrix.Rows);
            Vector result = GetVector(notPDMatrix.Rows);
            notPD.Solve(vector, result);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SolveVectorNullResult1()
        {
            Vector vector = null;
            Vector result = GetVector(matrix.Rows);
            cholesky.Solve(vector, result);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SolveVectorNullResult2()
        {
            Vector vector = GetVector(matrix.Rows);
            Vector result = null;
            cholesky.Solve(vector, result);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SolveVectorNotConformableResult1()
        {
            Vector vector = GetVector(matrix.Rows + 1);
            Vector result = GetVector(matrix.Rows);
            cholesky.Solve(vector, result);
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void SolveVectorNotConformableResult2()
        {
            Vector vector = GetVector(matrix.Rows);
            Vector result = GetVector(matrix.Rows + 1);
            cholesky.Solve(vector, result);
        }

    }
}
